/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package core;

import java.util.Observable;
import org.apache.log4j.Logger;
import org.apache.commons.lang.StringEscapeUtils;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
//import org.w3c.dom.Attr;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.DocumentBuilder;
import java.io.File;
import java.math.BigDecimal;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;

/**
 *
 * @author Sakis Vtdk
 */
public class XmlParser extends Observable
{
	/**
	 * Private constructor, needed to make use of the Singleton Pattern
	 * Use getXmlParser() instead
	 */
	private XmlParser()
	{
		this.addObserver(new ChangesObserver());
	}

	/**
	 * Method to get the instance of the XmlParser, used to implement
	 * the Singleton Pattern
	 * 
	 * @return XmlParser object
	 */
	public static XmlParser getXmlParser()
	{
		if(XmlParser._instance == null)
		{
			XmlParser._instance = new XmlParser();
		}

		return XmlParser._instance;
	}

	/**
	 * Reads an XML file into a Document (i.e. xmlDoc)
	 * 
	 * @param filename The path to the filename to be read
	 */
	public void loadXmlFile(String filename)
	{
		this.setFileName(filename);
		
		try
		{
			//open the requested file
			File xmlFile = new File(this._fileName);

			//create the docBuilder which will parse the file
			DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();

			//parse the file to the xmlDoc
			this._xmlDoc = docBuilder.parse(xmlFile);
			//this._xmlDoc.getDocumentElement().normalize(); //TODO: find out what normalize does
			
			log.info("Xml file loaded");
		}
		catch(Exception e)
		{
			log.fatal("Could not load the xml file '"+ filename +"'. Error: "+ e.getMessage());
		}
	}

	/**
	 * Parses the Document created by loadXmlFile() for the changes contained
	 */
	public void parseXmlChanges()
	{
		//ChangesLogger changesLogger = ChangesLogger.getChangesLogger();

		log.info("Start parsing XML file for changes...");
	
		//get root Element
		Element rootElement = this._xmlDoc.getDocumentElement();

		NodeList changes = rootElement.getElementsByTagName(XmlGenerator.CHANGE_ELEMENT);
		log.info("Changes found in XML: "+ changes.getLength());

		for(int i = 0; i < changes.getLength(); i++)
		{
			Element change = (Element) changes.item(i);

			log.debug("Parsing change number: "+ (i+1) +" of "+ changes.getLength());
			Change ch = parseChange(change);

			setChanged();
			notifyObservers(ch);
		}
		
	}

	/**
	 * Parses the XML element 'change' to a Change object and returns it
	 * 
	 * @param changeElement
	 * @return Change
	 */
	private Change parseChange(Element changeElement)
	{
		Change change = new Change();

		Element parentElement = (Element) changeElement.getParentNode();

		//parse version element
		if(parentElement.getNodeName().equalsIgnoreCase(XmlGenerator.VERSION_ELEMENT))
		{
			String targetVersion = parentElement.getAttribute(XmlGenerator.TARGET_VERSION_ATTR);

			change.setTargetVersion(new BigDecimal(targetVersion));
		}

		//First parse the xml for the data regarding all kinds of changes
		//set the type of change
		try
		{
			log.debug("Setting 'type'");
			change.setType(changeElement.getAttribute(XmlGenerator.CHANGE_TYPE_ATTR));
		}
		catch(Exception e)
		{
			log.warn(e.getMessage());
		}

		//set the object that has been changed
		try
		{
			log.debug("Setting 'objectChanged'");
			change.setObjectChanged(getElementValue(changeElement.getElementsByTagName(XmlGenerator.OBJECT_CHANGED_ELEMENT).item(0)));
		}
		catch(Exception e)
		{
			log.warn(e.getMessage());
		}

		//set the parentId
		log.debug("Setting 'parent_id'");
		change.setParentId(Integer.parseInt(getElementValue(changeElement.getElementsByTagName(XmlGenerator.PARENT_ID_ELEMENT).item(0))));

		//set the parent name
		if(changeElement.getElementsByTagName(XmlGenerator.PARENT_NAME_ELEMENT).getLength() != 0)
		{
			log.debug("Setting 'parent_name'");
			change.setParentName(getElementValue(changeElement.getElementsByTagName(XmlGenerator.PARENT_NAME_ELEMENT).item(0)));
		}
		//set the id
		log.debug("Setting 'id'");
		change.setId(Integer.parseInt(getElementValue(changeElement.getElementsByTagName(XmlGenerator.ID_ELEMENT).item(0))));

		//set the name
		log.debug("Setting 'name'");
		change.setName(getElementValue(changeElement.getElementsByTagName(XmlGenerator.NAME_ELEMENT).item(0)));

		//and now parse if change is "UPDATE"
		if(change.getType().equalsIgnoreCase("UPDATE"))
		{
			//we set which part of our element has changed
			log.debug("Setting 'what_changed'");
			change.setWhatChanged(getElementValue(changeElement.getElementsByTagName(XmlGenerator.WHAT_CHANGED_ELEMENT).item(0)));	
			
			//we set the previous value
			log.debug("Setting 'old_value'");
			change.setOldValue(getElementValue(changeElement.getElementsByTagName(XmlGenerator.OLD_VALUE_ELEMENT).item(0)));

			//we set the new value
			log.debug("Setting 'new_value'");
			change.setNewValue(getElementValue(changeElement.getElementsByTagName(XmlGenerator.NEW_VALUE_ELEMENT).item(0)));
		}

		//set the summary for each change
		log.debug("Setting 'summary'");
		change.setChangeSummary(getElementValue(changeElement.getElementsByTagName(XmlGenerator.SUMMARY_ELEMENT).item(0)));

		return change;
	}


	/**
	 * Method returns the value from an element written as 
	 * <code> <element>VALUE</element> </code>
	 * 
	 * @param element Must be of the form <code><element>VALUE</element></code>
	 * @return String - the value of the element
	 */
	private String getElementValue(Node node)
	{
		Element element = (Element) node;

		return StringEscapeUtils.unescapeXml(element.getChildNodes().item(0).getNodeValue());
	}

	/**
	 * Returns the root node of the current document
	 * @return The root node of the document
	 */
	public Node getRootNode()
	{
		return this._xmlDoc.getDocumentElement();
	}
	
	//<editor-fold desc="Getters">

	public String getFileName()
	{
		return this._fileName;
	}

	//</editor-fold>

	//<editor-fold desc="Setters">

	/**
	 * Private because the setting of the fileName will be done via the parseXmlFile()
	 * 
	 * @param fileName 
	 */
	private void setFileName(String fileName)
	{
		this._fileName = fileName;
	}

	//</editor-fold>

	private static XmlParser _instance = null;
	private String _fileName;
	private Document _xmlDoc;

	private static Logger log = Logger.getLogger(XmlParser.class);
}
